//
//  DBConnection.swift
//  SLDatabase
//
//  Created by 姚胜龙 on 2017/7/3.
//  Copyright © 2017年 姚胜龙. All rights reserved.
//

/**
 *  DBConnection 数据库连接类
 *  枚举定义 数据库的存储方式：1、内存数据库 2、临时数据库 3、本地数据库
 *  技术手段：链式编程
 */

import UIKit

//数据库连接类 不允许继承， 允许在任何的模块调用
public final class DBConnection {

    //1、––––––数据库的存储方式(定义方式可以是ENUM或者常量)–––––––––
    public enum DBLocation : CustomStringConvertible{
        //内存（文件名称 ":memory:"）
        case inMemary
        //临时(文件名称 "")
        case inTemporary
        //数据库路径(文件名称："路径")
        case URI(String)

        //处理数据库的文件名称 (返回具体的内容)
        public var description: String {
            switch self {
            case .inMemary:
                return ":memory:"
            case .inTemporary:
                return ""
            case .URI(let dbPath):
                return dbPath
            }
        }
    }

//    2、–––––––––––––––––定义数据库操作类型––––––––––––––––––––––––
//    为什么要设计为枚举？因为数据库的操作对应的是Int32位常量
//    定义枚举是方便扩展和识别操作->(更加的直观，增强可读性)
    public enum Operation {
        //插入操作->intert
        case insert
        //删除操作->delete
        case delete
        //更新操作->update
        case update
        //查询操作->select
        case select

        //将数据库的类型->转成->我们自定义的枚举
        fileprivate init (value:Int32) {
            switch value {
            case SQLITE_INSERT:
                self = .insert
                break
            case SQLITE_DELETE:
                self = .delete
                break
            case SQLITE_UPDATE:
                self = .update
                break
            case SQLITE_SELECT:
                self = .select
                break
            default:
                fatalError("没有这个数据库操作类型：\(value)")
            }
        }
    }

    /**
     * 知识点说明
     * 注意：如果一个类继承了NSObject定义变量,可以直接使用成员变量，不需要使用懒加载
     * fileprivate var ququeContext:Int = unsafeBitCast(self, to: Int.self)
     * 否则就要使用懒加载 (因为对象没有初始化)
     * fileprivate lazy var ququeContext:Int = unsafeBitCast(self, to: Int.self)
     */

    //3、–––––––––––––––构建数据库的连接（构造方法）––––––––––––––
    var handle:OpaquePointer? = nil
    fileprivate var queue = DispatchQueue(label: "sqlite") //数据库的队列标记
    //判断key是否在同一个队列
    fileprivate static let queueKey = DispatchSpecificKey<Int>()
    //给队列设计一个标记 也就是队列的上下文
    fileprivate lazy var ququeContext:Int = unsafeBitCast(self, to: Int.self)

    //数据库打开的时候有可能抛出异常 throws
    init(_ dbLocation:DBLocation = .inMemary, readonly:Bool = false) throws {
        //打开数据库
        //参数1：数据库的类型->filename: UnsafePointer<Int8>!
        //参数2：数据库的指针（指向表）
        //参数3：数据库的权限（标记）
        //参数4：传nil就可以了
        // SQLITE_OPEN_FULLMUTEX：标示串行结构，一个一个执行
        let flag = readonly ? SQLITE_OPEN_READONLY : (SQLITE_OPEN_READWRITE|SQLITE_OPEN_CREATE)

        try self.checkError(sqlite3_open_v2(dbLocation.description, &handle, flag|SQLITE_OPEN_FULLMUTEX, nil))

        //给数据库设计队列标记
        //目的：操作数据库的时候避免并发（多个子线程 -> 同时操作数据库 ->保证安全）
        queue.setSpecific(key: DBConnection.queueKey, value: ququeContext)

    }

    //传递数据库文件 构造方法
    convenience init(_ fileName:String, readonly:Bool = false) throws{
        //捕获异常
        try self.init(.URI(fileName), readonly:readonly)
    }

    //4、––––––––––––––––数据库异常信息处理––––––––––––––––––––––––––
    //将数据库的报错信息-->转换-->可读性强|可理解的异常信息
    //数据库的异常信息都是->宏定义 可读性非常的差

    //检查数据库返回的code
    @discardableResult func checkError(_ resultCode: Int32) throws -> Int32 {
        //如果没有异常，就将结果返回, 否则抛出异常
        guard let error = DBResult(errorCode:resultCode, connection: self) else {
            return resultCode
        }
        throw error
    }

    //5、––––––––––––––––––––––––检查数据库的操作––––––––––––––––––––––––
    //获取数据库权限
    public var readonly:Bool {
        return sqlite3_db_readonly(handle, nil) == 1
    }

    //当前数据库插入最近一条数据的ID
    public var changes:Int {
        return Int(sqlite3_changes(handle))
    }
    //获取从打开数据库到目前为止，操作表受影响的行数
    public var totalChanges:Int {
        return Int(sqlite3_total_changes(handle))
    }
    //等等...

    //第六步：执行SQL语句优化-> 绑定参数
    /********************执行SQL语句******start******************/
    //绑定的参数有三种：1、可变参数 2、数组参数 3、字典参数 -> 方法重载

    //参数使用"Any"类型存在的问题？
    //导致参数很难扩展（例如：数据库表字段名称、数据库表字段类型、数据库表字段类型对应到swift语言中的类型...）
    //解决方案：使用扩展协议 （面向协议编程）
    //要求：即使用户传的参数顺序是乱的也没有关系，我也要做到一一对应
    
    //可变参数 其实就是数组
    @discardableResult public func runSQL(_ statement: String, _ bindings: Binding?...) throws -> Statement {
        return try self.runSQL(statement, bindings)
    }
    //数组参数
    @discardableResult public func runSQL(_ statement: String, _ bindings: [Binding?]) throws -> Statement{
        //构建SQL -> 执行SQL
        return try self.prepareSQL(statement).executeSQL(bindings)
    }
    //字典参数
    @discardableResult public func runSQL(_ statement: String, _ bindings: [String : Binding?]) throws -> Statement {
        //构建SQL -> 执行SQL
        return try self.prepareSQL(statement).executeSQL(bindings)
    }
    /*******************执行SQL语句******end****************/

    /*******************SQL语句绑定参数******start****************/

    public func prepareSQL(_ statement: String, _ bindings: Binding...) throws  -> Statement
    {
        if !bindings.isEmpty {
            return try self.prepareSQL(statement, bindings)
        }
        return try Statement(self, statement)
    }

    public func prepareSQL(_ statement: String, _ bindings: [Binding?]) throws -> Statement
    {
        return try self.prepareSQL(statement).bind(bindings)
    }

    public func prepareSQL(_ statement: String, _ bindings: [String: Binding?]) throws -> Statement
    {
        return try self.prepareSQL(statement).bind(bindings)
    }

    /*******************SQL语句绑定参数******end****************/



    //6、––––––––––––––––执行SQL语句––––––––––––––––––––––––
    public func execute(_ sql:String) throws {
        //在这里队列就派上用场了
        try self.checkError(sqlite3_exec(handle, sql, nil, nil, nil))
    }

    //保证同步 -> （传递闭包，保证同步）
    //方法泛型
    public func dbSync<T>(_ callBack:@escaping () throws -> T) rethrows ->T {
        //可以接受任何类型
        var success:T?
        //可以接受任何错误类型
        var failure:Error?

        //定义临时代码块，用户处理异常结果 自动闭包
        let box:()->Void = {

            do {
               success = try callBack()
            } catch {
                failure = error
            }
        }

        //如果是当前队列，那么就直接执行，否则就使用新的队列执行
        if DispatchQueue.getSpecific(key: DBConnection.queueKey) == ququeContext {
            box() //立即执行
        } else {
            queue.sync(execute: box)
        }

        //如果执行报错了
        if (failure != nil) {
            //自动闭包抛异常
            try {()->Void in
                throw failure!
            }()
        }
        //否则返回成功
        return success!
    }

}

//定义数据库返货code的枚举
public enum DBResult:Error {
    //如何表示数据库成功？
    //SQLITE_OK:表示执行当前操作成功
    //SQLITE_ROW:表示执行sqlite3_step()函数成功，受影响行数
    //SQLITE_DONE:表示执行sqlite3_step()完成
    fileprivate static let successCode = [SQLITE_OK, SQLITE_ROW, SQLITE_DONE]
    case error(message:String, code:Int32)

    //构造方法加 “？”表示这个枚举是可选类型，可以为nil
    init?(errorCode:Int32, connection:DBConnection) {
        guard !DBResult.successCode.contains(errorCode) else {
            //表示成功了
            return nil
        }

        //如果失败了，将错误信息处理
        let message = String(cString: sqlite3_errmsg(connection.handle))
        self = .error(message: message, code: errorCode)
    }
}



